# !/usr/bin/env python
# -*-coding:utf-8 -*-

"""
# File       : process_parse.py
# Time       ：2023/8/1 10:26
# Author     ：YangYong
# version    ：python 3.10
# Description：
解析流程
方式一:
1、使用flask线程隔离的方式进行隔离线程
2、使用队列的方式运行任务的取出和放入，这里主要考虑到多线程下的数据安全
方式二：
使用redis stream的方式，提高系统的高可用性，保证系统在异常情况下依然可以正确的运行流程
其中AppContext中的数据也在redis中保存一份，避免系统宕机数据丢失
Parser process

1. Use flask thread isolation to isolate threads
2, the use of queues to run the extraction and insertion of tasks, mainly considering the data security
 under multi-threading
"""
from abc import abstractmethod

from conf.config import OrderLinesConfig
from orderlines.execute.app_context import AppContext
from orderlines.execute.base_parse import BaseParse
from orderlines.execute.running_db_operator import RunningDBOperator
from orderlines.utils.base_orderlines_type import ProcessControlParam
from orderlines.utils.exceptions import OrderlinesHasNoStart
from public.utils.task_queue import RedisQueue, LocalQueue


class ProcessParse(BaseParse):
    def __init__(self, process_instance_id: str, context: AppContext):
        super(ProcessParse, self).__init__(process_instance_id, context)
        # 初始化，开始当前执行id为start node id
        self.current_task_id = self.start_node_id()
        process_id = self.context.get_process_item(self.process_instance_id, 'process_id')
        self.running_db_operator: RunningDBOperator = RunningDBOperator(process_instance_id, process_id)
        self.stream_name = f'orderlines:{self.process_name}:{self.process_instance_id}'

    def get_next_node_id(self):
        """
        获取下一个要运行的任务
        get next will run task
        @return:
        """
        for node in self.task_nodes:
            if node.get('task_id') == self.current_task_id:
                if node.get('task_type') == 'process_control':
                    process_control_kw: dict = node.get('method_kwargs')
                    process_control_kw.setdefault('process_info', self.process_info)
                    process_control_param = ProcessControlParam(**process_control_kw)
                    from orderlines.libraries.ProcessControl import ProcessControl
                    return ProcessControl().process_control(process_control_param)

                return node.get('next_id')
        return None

    def start_node_id(self) -> str:
        """
        获取流程开始节点
        Gets the start node in the process
        @return:
            返回流程的开始节点id
            Returns the id of the start node of the process
        """
        for node in self.task_nodes:
            if node.get('method_name') == 'start':
                return node.get('task_id')
        raise OrderlinesHasNoStart('orderlines process has no start node')

    @abstractmethod
    def generate(self) -> bool:
        """生成任务"""
        pass

    @abstractmethod
    def consumer(self) -> str:
        """消费任务,返回当前任务id"""
        pass

    @abstractmethod
    def is_complete(self) -> bool:
        """任务队列是否为空, 空为完成，不空没有完成"""
        pass


class RedisStreamParse(ProcessParse):
    def __init__(self, process_instance_id: str, context: AppContext):
        super(RedisStreamParse, self).__init__(process_instance_id, context)
        self.group_name = f'group:{self.process_name}'
        self.consumer_name = f'consumer:{self.process_name}'
        self.task_queue = RedisQueue()
        self.task_queue.create_group(self.stream_name, self.group_name)
        self.task_queue.add(self.stream_name, {'task_id': self.current_task_id})
        self.message_id = ''

    def generate(self):
        """
        使用redis Stream方式进行存放数据
        @return:
        """
        _next_task_id = self.get_next_node_id()
        if not _next_task_id:
            return False
        else:
            self.task_queue.add(self.stream_name, {'task_id': _next_task_id})
            self.current_task_id = _next_task_id
            return True

    def consumer(self):
        message = self.task_queue.group_read(self.stream_name, self.consumer_name, self.group_name)
        if not message:
            self.message_id = None
            return None
        assert len(message) == 1
        message = message.pop()
        self.message_id = message.get('message_id')
        return message.get('message', {}).get('task_id')

    def ack(self, task_id: str):
        """对于任务进行ack确认"""
        self.task_queue.ack(self.stream_name, self.group_name, task_id, delete=True)

    def is_complete(self):
        return not self.task_queue.is_empty(self.stream_name)


class LocalProcessParse(ProcessParse):
    def __init__(self, process_instance_id: str, context: AppContext):
        super(LocalProcessParse, self).__init__(process_instance_id, context)
        self.task_queue = LocalQueue()
        self.task_queue.add(self.stream_name, {'task_id': self.current_task_id})
        self.message_id = self.current_task_id

    def generate(self) -> bool:
        """解析任务节点"""
        _next_task_id = self.get_next_node_id()
        # self.process_state_machine.trigger_chance('start')
        if not _next_task_id:
            return False
        else:
            self.task_queue.add(self.stream_name, {'task_id': _next_task_id})
            self.current_task_id = _next_task_id
            return True

    def consumer(self) -> str:
        task_data = self.task_queue.get(self.stream_name)
        return task_data.get('task_id') if task_data else None

    def is_complete(self):
        return not self.task_queue.is_empty(self.stream_name)

    def ack(self, task_id: str):
        """对于任务进行ack确认"""
        self.task_queue.ack(self.stream_name, task_id)


class TaskPatternContext:
    def __init__(self, process_instance_id: str, context: AppContext):
        if OrderLinesConfig.task_pattern == 'local':
            self.task_parse = LocalProcessParse(process_instance_id, context)
        else:
            self.task_parse = RedisStreamParse(process_instance_id, context)

    def generate_task(self) -> bool:
        """返回当前流程是否有其他任务节点，如果有后续任务节点则返回True,没有后续任务则返回False"""
        return self.task_parse.generate()

    def consumer_task(self) -> str:
        return self.task_parse.consumer()

    def message_id(self):
        return self.task_parse.message_id
